<?php


namespace commands;


use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

class BaseCommand extends Command
{

    protected static $defaultName = 'base';
    public $departments = [];
    public $cities = [];
    public $flights = [];
    public $data = [];
    public $data2 = [];
    const DEFAULT_DIR = '';
    public $missFlight = [];

    public function configure()
    {
        parent::configure();
        $this->addArgument('dir', InputArgument::REQUIRED, 'csv的文件夹');
        //$this->addArgument('output', InputArgument::OPTIONAL, '输出文件夹', self::DEFAULT_DIR);
    }


    public function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('开始运行');
        $output->writeln('载入机构数据');
        $this->doCsv('./tpl/departments.csv', $output, [$this, 'ProcessDepartment']);
        $output->writeln(sprintf('<info>一共%d个州市，%d个机构</info>', count(array_flip($this->cities)), count(array_flip($this->departments))));
        $output->writeln('载入航班数据');
        $this->doCsv('./tpl/flights.csv', $output, function ($row, $n) {
            if ($n == 0) return;
            $code = strtoupper(trim($row[0]));
            $code && $this->flights[$code] = floatval($row[1]);
        });
        $output->writeln(sprintf('<info>一共%d个航班信息</info>', count($this->flights)));
    }

    public function doCsv(string $csv, OutputInterface $output, callable $process, $maxLine = 0)
    {
        $lines = 0;
        $fp = fopen($csv, 'r');
        if (!$fp) exit(sprintf('无法打开：%s', $csv));
        while (!feof($fp)) {
            fgets($fp);
            $lines++;
        }
        if ($lines == 0) {
            fclose($fp);
            return false;
        }
        $progressBar = new ProgressBar($output, $lines);
        $progressBar->setFormat('verbose');
        $progressBar->start();
        rewind($fp);
        $n = 0;
        $per = floor($lines / 100);
        $per = $per < 1 ? 1 : $per;
        while (!feof($fp)) {
            $row = fgetcsv($fp);
            if (is_array($row) && !(count($row) == 1 && $row[0] == null)) {
                call_user_func($process, $row, $n);
            }
            $n++;
            if ($n % $per === 0) {
                $progressBar->advance($per);
            }
            if ($maxLine > 0 && $n > $maxLine) break;
        }
        $progressBar->finish();
        $output->writeln('');
        fclose($fp);
    }

    public function ProcessDepartment($row, $n)
    {
        if ($n == 0) return;
        $code = strtoupper(trim($row[0]));;
        $city = iconvStr($this->getCellFromRow($row, 'B'));
        $department = iconvStr($this->getCellFromRow($row, 'C'));
        if ($city) {
            $this->cities[$code] = $city;
        }
        if ($department) {
            $this->departments[$code] = $department . '[' . $code . ']';
            //$this->departments[$code] = '[' . $code . ']';
        }

    }

    function getCityName($code)
    {
        $code = strtoupper($code);
        if (!isset($this->cities[$code])) {
            throw new \Exception(sprintf('缺失州市：%s', $code));
        }
        return $this->cities[$code];
    }

    function getDepartmentName($code)
    {
        $code = strtoupper($code);
        if (!isset($this->departments[$code])) {
            throw new \Exception(sprintf('缺失机构：%s', $code));
        }
        return $this->departments[$code];
    }

    public function displayTable(OutputInterface $output, array $data, array $headers = [])
    {
        $table = new Table($output);
        !empty($headers) && $table->setHeaders($headers);
        $table->setRows($data);
        $table->render();
    }

    public function getFlightPrice($flight)
    {
        $flight = strtoupper($flight);
        if (isset($this->flights[$flight])) {
            return $this->flights[$flight];
        } else {
            $this->missFlight[$flight] = 0;
            //throw new \Exception(sprintf('没有航班"%s"的价格', $flight));
        }
        return 0;
    }

    public function processField($field, $identity, $row, $n)
    {
        try {
            $city = $this->getCityName($identity);
            $department = $this->getDepartmentName($identity);
        } catch (\Exception $exception) {
            throw new \Exception($exception->getMessage() . sprintf('%d行', $n));
        }
        if (!isset($this->data[$city][$field])) {
            $this->data[$city][$field] = 0;
        }
        if (!isset($this->data2[$department][$field])) {
            $this->data2[$department][$field] = 0;
        }

        $r = call_user_func([$this, $field], $row, $n, $identity);
        $this->data[$city][$field] += floatval($r);
        $this->data2[$department][$field] += floatval($r);
        return $r;
    }

    public function processRow($field, $row, $n)
    {
        if (!isset($this->data[$field])) {
            $this->data[$field] = 0;
        }
        if (!isset($this->data2[$field])) {
            $this->data2[$field] = 0;
        }
        $r = call_user_func([$this, $field], $row, $n);
        $this->data[$field] += floatval($r);
        $this->data2[$field] += floatval($r);

    }

    public function getDataFilePath($path)
    {
        return sprintf('./data/%s', ltrim($path, '/'));

    }

    public function extractZip($zipFile, OutputInterface $output, $dest)
    {
        $zip = new \ZipArchive();
        if ($zip->open($zipFile) === true) {
            $progressBar = new ProgressBar($output, $zip->numFiles);
            $progressBar->setFormat('verbose');
            $progressBar->start();
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $file = $zip->getNameIndex($i);
                $convFile = iconvStr($file);
                //$zip->renameName($file, $convFile);
                if (substr($convFile, -1, 1) == '/') continue;

                //var_dump($file);
                try {
                    if (strpos($file, '..') === false) {
                        @$zip->extractTo($dest, $file);
                    } else {
                        //@$zip->extractTo($dest, $file);
                        $content = $zip->getFromIndex($i);
                        $o = $dest . DIRECTORY_SEPARATOR . trim($file, '.');
                        @mkdir(dirname($o), 0777, true);
                        file_put_contents($o, $content);
                        //copy("zip://" . $zipFile . "#" . $file, $dest . DIRECTORY_SEPARATOR . trim($file, '.'));
                    }
                } catch (\Exception $exception) {
                    $output->writeln($exception->getMessage());
                }
                //rename($dest.DIRECTORY_SEPARATOR.$file, $dest.DIRECTORY_SEPARATOR.$convFile);
                $progressBar->advance();
            }

            $progressBar->finish();
            $output->writeln('');
            $zip->close();
        } else {
            $output->writeln(sprintf('无法打开压缩包：%s'), $zipFile);
        }

    }

    public function unzip($files, $dir, OutputInterface $output)
    {
        $data = [];
        $finder = new Finder();
        foreach ($files as $filename) {
            $findFile = $this->findFile($dir, $filename);
            if ($findFile === null) {
                $output->writeln(sprintf('无法在%s找到压缩文件：%s', $dir, $filename));
                exit();
            }
            $zipPath = $findFile->getPathname();
            $zipName = $findFile->getFilename();
            $output->writeln('<info>解压文件：' . $zipPath . '</info>');
            $dest = $dir . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $zipName;//多个压缩包
            $this->extractZip($zipPath, $output, $dest);
            $data[] = $dest;
        }
    }

    /**
     * @param $dir
     * @param $name
     * @param string $deep
     * @param string $filter
     * @return SplFileInfo||null
     */
    public function findFile($dir, $name, $deep = '==0', $path = '', $filter = '')
    {
        $finder = new Finder();
        $finder->name($name)->in($dir)->depth($deep);
        if ($path) $finder->path($path);
        foreach ($finder->files() as $file) {
            /**
             * @var SplFileInfo $file
             */
            return $file;
        }
        return null;
    }

    /**
     * @param $dir
     * @param $name
     * @param string $deep
     * @param string $path
     * @param string $filter
     * @return SplFileInfo
     * @throws \Exception
     */
    public function mustFindFile($dir, $name, $deep = '==0', $path = '', $filter = '')
    {
        $file = $this->findFile($dir, $name, $deep, $path, $filter);
        if (is_null($file)) {
            throw new \Exception(sprintf('没有在%s下找到文件：%s', $dir, $name));
        }
        return $file;
    }

    public function getCellFromRow($row, $col)
    {
        $index = getColIndex($col);
        if (!isset($row[$index])) {
            throw new \Exception(sprintf('没有这个列：%s', strtoupper($col)));
        }
        return trim($row[$index]);
    }

    public function export($dest, $tpl, callable $callback)
    {
        if (!is_file($tpl)) {
            echo '没有模板文件';
            exit();
        }
        $reader = new Xlsx();
        $spreadsheet = $reader->load($tpl);
        $sheet = $spreadsheet->getActiveSheet();
        $first_row = $this->findFirstRowToRender($sheet);
        call_user_func($callback, $spreadsheet);
        $styleArray = array(
            'borders' => array(
                'allBorders' => array(
                    'borderStyle' => Border::BORDER_THIN,
                ),
            ),
        );

        $sheet->getStyle(sprintf('A%d:%s%d', $first_row, $sheet->getHighestColumn(), $sheet->getHighestRow()))->applyFromArray($styleArray);
        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
        $writer->save($dest);
        $output = new ConsoleOutput();
        $output->writeln(sprintf('<info>成功保存文件：%s</info>', $dest));
    }

    public function __destruct()
    {
        if (!empty($this->missFlight)) {
            echo '缺失以下航班信息：' . PHP_EOL;
            foreach ($this->missFlight as $k => $v) {
                echo $k . PHP_EOL;
            }
        }
    }

    public function exportPhp($id, $var)
    {
        file_put_contents('./data/' . $id . '.php', '<?php' . "\r\n" . var_export($var, true));
    }

    public function findFirstRowToRender(Worksheet $sheet)
    {
        for ($i = 3; $i++; $i < 20) {
            if (!$sheet->cellExists('A' . $i)) {
                return $i;
            }
        }
    }

    public function findLastRow(Worksheet $sheet)
    {
        for ($i = 3; $i++; $i < 20000) {
            if (!$sheet->cellExists('A' . $i)) {
                return $i - 1;
            }
        }
    }

    public function findLastColName(Worksheet $sheet)
    {
        $first_row = $this->findLastRow($sheet);
        foreach (range('A', 'Z') as $col) {
            if (!$sheet->cellExists($col . $first_row)) {
                return $col;
            }
        }
        throw new \Exception('没有找到列结尾');
    }

    public function setSheetSubtotal(Worksheet $sheet, $first_row, $last_row = '', $highestCol = '', &$subtotal = [])
    {
        $last_row = $last_row ? intval($last_row) : $sheet->getHighestRow();
        $highestCol = $highestCol ? $highestCol : $sheet->getHighestColumn();
        $sheet->setCellValue('A' . $last_row, '总计');
        $sheet->getStyle('A' . $last_row)->getFont()->setBold(true);
        foreach (range('B', strtoupper($highestCol)) as $c) {
            $sheet->setCellValue($c . $last_row, sprintf('=SUM(%s%d:%s%d)', $c, $first_row, $c, $last_row - 1));
            $subtotal[$c] = $sheet->getCell($c . $last_row)->getCalculatedValue();
        }
    }

    public function rmTemp(string $dir, OutputInterface $output)
    {
        $output->writeln('删除解压文件');
        try {
            $fs = new Filesystem();
            $fs->remove($dir . DIRECTORY_SEPARATOR . 'temp');
        } catch (\Exception $exception) {
            $output->writeln($exception->getMessage());
        }
    }


}
